<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */

namespace App\Websocket;

use App\Logic\UserLogic;
use App\Model\Live;
use App\Model\LiveChat;
use App\Model\Play;
use App\Model\User;
use App\Model\UserToken;
use App\Utils\Enum\UserEnum;
use App\Utils\Msg;
use Hyperf\Contract\OnCloseInterface;
use Hyperf\Contract\OnMessageInterface;
use Hyperf\Contract\OnOpenInterface;
use Hyperf\Engine\WebSocket\Frame;
use Hyperf\Engine\WebSocket\Response;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Redis\Redis;
use Hyperf\WebSocketServer\Constant\Opcode;
use Hyperf\WebSocketServer\Context;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use RedisException;
use Swoole\WebSocket\Server as WebSocketServer;

class ChatWebsocket implements OnMessageInterface, OnOpenInterface, OnCloseInterface
{
    public const int HEARTBEAT_INTERVAL = 10;
    public const string KEY_FDS = 'fds:chat';
    public const string KEY_FD_INFO = 'fd:';
    public const string KEY_GROUP = 'group:';

    protected LoggerInterface $logger;

    protected ContainerInterface $container;

    protected Redis $redis;

    protected string $heartbeatMsg;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
        try {
            $this->logger = $this->container->get(LoggerFactory::class)->get('live-chat', 'default');
            $this->redis = $this->container->get(Redis::class);

            $this->heartbeatMsg = Msg::Create('UserHeartbeatCmd');
        } catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) {
        }
    }

    public function onMessage($server, $frame): void
    {
        if ($frame->opcode == Opcode::PING) {
            $response = (new Response($server))->init($frame);
            $response->push(new Frame(opcode: Opcode::PONG));
            return;
        }

        try {
            $msg = Msg::Parse($frame);
            if ($msg) {
                $this->logger->info(sprintf('收到%s消息 %s', $msg['msgType'], new_json_encode($msg['msgBody'])));
                $action = '_event' . ucfirst($msg['msgType']);
                if (method_exists($this, $action)) {
                    call_user_func_array([$this, $action], [$server, $frame->fd, $msg['msgBody']]);
                } else {
                    $this->logger->warning("未知消息类型:{$msg['msgType']}");
                }
            } else {
                $this->logger->warning('消息解析失败');
            }
        } catch (\Exception $e) {
            echo $e->getFile() . "\n" . $e->getLine() . "\n";
            $this->logger->warning('消息异常:' . $e->getMessage());
        }
    }

    /**
     * 登录
     * @param WebSocketServer $server
     * @param $fd
     * @param $data
     * @return void
     */
    private function _eventUserEntryCmd(WebSocketServer $server, $fd, $data): void
    {
        if (empty($data['matchId'])) {
            $this->logger->warning('match_id参数缺失');
            return;
        }
        $userInfo = $this->_parseUserInfo($data);
        if ($userInfo === false) {
            $this->logger->warning('用户信息解析失败');
            return;
        }

        $userInfo['level'] = UserLogic::getUserLevelInfo($userInfo['integral'])['levelName'];

        if (preg_match('/^\d+$/', $data['matchId'])) {
            $live = Live::query()->where('room_id', $data['matchId'])->first()->toArray();
            if (empty($live)) {
                $this->logger->warning(sprintf('直播间%d不存在', $data['matchId']));
                return;
            }
            $chatGroupId = $live['room_id'] . '-' . $live['session_id'];
            Context::set('roomId', $live['room_id']);
            Context::set('matchId', $live['match_id']);
        } else {
            if (!Play::query()->where('match_id', $data['matchId'])->exists()) {
                $this->logger->warning(sprintf('比赛%s不存在', $data['matchId']));
                return;
            }
            $chatGroupId = $data['matchId'];
            Context::set('matchId', $data['matchId']);
        }
        Context::set('userId', $userInfo['id']);
        Context::set('userInfo', $userInfo);
        Context::set('groupId', $chatGroupId);

        try {
            $this->redis->sAdd(self::KeyGroup($chatGroupId), $fd);
            $this->redis->hSet(self::KeyFd($fd), 'groupId', $chatGroupId);
        } catch (RedisException $e) {
            $this->logger->error(sprintf('redis操作异常:%s', $e->getMessage()));
        }
        $msg = Msg::Create('UserEntryResult', [
            'code' => 200,
            'userId' => $userInfo['id'],
            'userNick' => $userInfo['nickname'],
            'userLevel' => str_replace('LV', '', $userInfo['level']),
            'userAvatar' => img_url($userInfo['avatar'], 60),
            'messageList' => $this->_getLastMessageList($chatGroupId, 50)
        ]);
        $server->push($fd, $msg, WEBSOCKET_OPCODE_BINARY);
    }

    /**
     * 发送消息
     * @param WebSocketServer $server
     * @param $fd
     * @param $data
     * @return void
     */
    private function _eventUserSendMessageCmd(WebSocketServer $server, $fd, $data): void
    {
        $chatGroupId = Context::get('groupId', '');
        $userInfo = Context::get('userInfo');
        if (empty($chatGroupId) || empty($userInfo)) {
            $server->push($fd, Msg::Create('UserSendMessageResult', [
                'code' => 403,
                'result' => '',
                'msg_id' => $data['msgId']
            ]), WEBSOCKET_OPCODE_BINARY);
            echo "code:403\n";
            return;
        }
        $errMsg = '';
        $msg = [
            'msg_id' => $data['msgId'],
            'group_id' => $chatGroupId,
            'user_id' => $userInfo['id'],
            'user_nick' => $userInfo['nickname'],
            'user_avatar' => $userInfo['avatar'],
            'user_type' => $userInfo['role_id'] == UserEnum::USER_TYPE_VISITOR ? 9 : 1,
            'user_level' => $userInfo['level'],
            'msg_type' => $data['msgType'],
            'msg_time' => time()
        ];
        if ($data['msgType'] == 'text') {
            //TODO 过滤非法关键词
            if (empty($data['msgData'])) {
                $errMsg = '消息内容不能为空';
            } else {
                $msg['msg_data'] = $data['msgData'];
            }
        }

        if (empty($errMsg)) {
            try {
                $chatMsg = LiveChat::create($msg);
            } catch (\Exception $e) {
                $this->logger->warning(sprintf('写入消息失败:%s', $e->getMessage()), $msg);
                $errMsg = '发送失败';
            }
        }
        //1-成功Ω 2-包含敏感词 3-敏感词 4-暂时不可发言  5连续发送重复内容 6发送太频繁 7-   1-成功
        $sendResultMsg = Msg::Create('UserSendMessageResult', [
            'code' => empty($errMsg) ? 7 : 1,
            'result' => $errMsg,
            'msg_id' => $data['msgId'],
        ]);
        echo "code:".(empty($errMsg) ? 200 : 400)."\n";
        $server->push($fd, $sendResultMsg, WEBSOCKET_OPCODE_BINARY);

        if (isset($chatMsg) && $chatMsg['id']) {
            $newMessage = Msg::Create('UserNewMessageResult', [
                'message_list' => [Msg::Pack('MessageData', [
                    'msg_id' => $msg['msg_id'],
                    'msg_type' => $msg['msg_type'],
                    'msg_data' => $msg['msg_data'],
                    'msg_time' => $msg['msg_time'],
                    'user_id' => $msg['user_id'],
                    'user_type' => $msg['user_type'],
                    'user_nick' => $msg['user_nick'],
//                    'user_avatar' => img_url($msg['user_avatar'], 60),
                    'user_avatar' => '',
                    'user_level' => str_replace('LV', '', $msg['user_level'])
                ])]
            ]);
            $this->_sendGroupMsg($server, $chatGroupId, $newMessage);
        }
    }

    private function _eventUserDoCmd($server, $fd, $data)
    {
    }

    /**
     * 心跳
     * @param WebSocketServer $server
     * @param $fd
     * @param $data
     * @return void
     */
    private function _eventUserHeartbeatCmd(WebSocketServer $server, $fd, $data): void
    {
        try {
            $this->redis->zAdd(self::KeyFds(), time(), (string)$fd);
        } catch (RedisException $e) {
            $this->logger->warning(sprintf('redis操作异常:%s', $e->getMessage()));
        }
        $server->push($fd, $this->heartbeatMsg, WEBSOCKET_OPCODE_BINARY);
    }


    /**
     * 获取近期消息列表
     * @param $chatGroupId
     * @param $num
     * @return array
     */
    private function _getLastMessageList($chatGroupId, $num): array
    {
        $rs = LiveChat::query()->where('group_id', $chatGroupId)->orderBy('id', 'desc')->limit($num)->get()->toArray();
        if (empty($rs)) {
            return [];
        }
        $msgList = [];
        foreach ($rs as $r) {
            array_unshift($msgList, Msg::Pack('MessageData', [
                'msg_id' => $r['msg_id'],
                'msg_type' => $r['msg_type'],
                'msg_data' => $r['msg_data'],
                'msg_time' => $r['msg_time'],
                'user_id' => $r['user_id'],
                'user_type' => $r['user_type'],
                'user_nick' => $r['user_nick'],
//                'user_avatar' => img_url($r['user_avatar']),
                'user_avatar' => '',
                'user_level' => str_replace('LV', '', $r['user_level'])
            ]));
        }
        return $msgList;
    }

    /**
     * 群发消息
     * @param WebSocketServer $server
     * @param $chatGroupId
     * @param $msg
     * @return int
     */
    private function _sendGroupMsg(WebSocketServer $server, $chatGroupId, $msg): int
    {
        $succ = 0;
        try {
            $fds = $this->redis->sMembers(self::KeyGroup($chatGroupId));
            if (!empty($fds)) {
                foreach ($fds as $fd) {
                    $ret = $server->push((int)$fd, $msg, WEBSOCKET_OPCODE_BINARY);
                    if ($ret) $succ++;
                }
            }
        } catch (RedisException $e) {
            //TODO
            $this->logger->warning(sprintf('Redis获取房间成员fd异常:%s', $e->getMessage()));
        }
        return $succ;
    }

    public static function KeyGroup($groupId): string
    {
        return self::KEY_GROUP . $groupId;
    }

    public static function KeyFd($fd): string
    {
        return self::KEY_FD_INFO . $fd;
    }

    public static function KeyFds(): string
    {
        return self::KEY_FDS;
    }

    /**
     * 解析用户信息
     * @param $loginData
     * @return array|false|mixed[]
     */
    private function _parseUserInfo($loginData): array|false
    {
        $fields = ['id', 'nickname', 'role_id', 'avatar', 'integral'];
        if (!empty($loginData['appUserId'])) {
            $tokenInfo = UserToken::query()->where('token', $loginData['appUserId'])->select(['userid'])->first();
            if (empty($tokenInfo)) {
                return false;
            }
            $userInfo = User::query()->where('id', $tokenInfo['userid'])->select($fields)->first();
            if($userInfo) {
                return $userInfo->toArray();
            } else {
                return false;
            }
        } elseif (!empty($loginData['deviceId'])) {
            $visitUserInfo = User::query()->where(['role_id' => UserEnum::USER_TYPE_VISITOR, 'device_id' => $loginData['deviceId']])->select($fields)->first();
            if($visitUserInfo) {
                return $visitUserInfo->toArray();
            } else {
                try {
                    $user = UserLogic::register([
                        'username' => 'U' . random(10),
                        'nickname' => '游客' . create_randomstr(6),
                        'roleId' => UserEnum::USER_TYPE_VISITOR,
                        'deviceId' => $loginData['deviceId'],
                        'password' => md5(create_randomstr(10)),
                        'client' => 0,
                        'ip' => '0.0.0.0'
                    ]);
                    if ($user['id']) {
                        $data = [];
                        foreach ($fields as $field) {
                            $data[$field] = $user[$field];
                        }
                        return $data;
                    } else {
                        return false;
                    }
                } catch (\Exception $e) {
                    echo $e->getFile()." ".$e->getLine();
                    $this->logger->warning(sprintf('注册游客账号异常:%s', $e->getMessage()));
                    return false;
                }
            }
        }
        return false;
    }

    public function onClose($server, int $fd, int $reactorId): void
    {
        try {
            $info = $this->redis->hGetAll(self::KeyFd($fd));
            if (!empty($info)) {
                if (!empty($info['groupId'])) {
                    $this->redis->sRem(self::KeyGroup($info['groupId']), $fd);
                }
            }
            $this->redis->del(self::KeyFd($fd));
            $this->redis->zRem(self::KeyFds(), (string)$fd);
            $this->logger->info(sprintf('连接%d主动断开', $fd));
        } catch (RedisException $e) {
            $this->logger->warning(sprintf('redis操作异常:%s', $e->getMessage()));
        }
    }

    public function onOpen($server, $request): void
    {
        try {
            $this->redis->zAdd(self::KEY_FDS, time(), (string)$request->fd);
            $this->redis->hSet(self::KeyFd($request->fd), 'openTime', time());
            $this->logger->info(sprintf('连接%d 连接成功', $request->fd));
        } catch (RedisException $e) {
            $this->logger->warning(sprintf('redis操作异常:%s', $e->getMessage()));
        }
    }
}
